<?php
/**
* phpCan - http://idc.anavallasuiza.com/
*
* phpCan is released under the GNU Affero GPL version 3
*
* More information at license.txt
*/

namespace data;

defined('ANS') or die();

class Table {
	private $Debug;
	private $Data;
	private $table;
	private $settings;


	/**
	 * public function __construct (object $Data, string $table, array $table_config, array $relation_config)
	 *
	 * return none
	 */
	public function __construct (\data\Data $Data, $table, $table_config, $relation_config) {
		global $Debug;

		$this->Debug = $Debug;
		$this->Data = $Data;
		$this->table = $table;

		$this->settings = array(
			'table' => $table_config,
			'relations' => $relation_config
		);

		if (!$this->settings['table']) {
			$this->Debug->fatalError(__('The configuration for the table "%s" does not exists!', $this->table));
		}
	}

	/**
	 * public function __get (string $name)
	 *
	 * return none
	 */
	public function __get ($name) {
		switch ($name) {
			case 'relations':
				$this->setRelationsConfig();
				return $this->relations;

			case 'formats':
				$this->setFormatsConfig();
				return $this->$name;
		}
	}


	/**
	 * public function fieldArray (string $name)
	 *
	 * return array
	 */
	public function fieldArray ($name) {
		if (!$name) {
			return array();
		}

		if (strpos($name, '[')) {
			preg_match('/([\w-]+)\[([\w-]+)\]/', $name, $match);

			return array(
				'realname' => trim($match[1]),
				'newname' => trim($match[2])
			);
		}

		return array(
			'realname' => $name,
			'newname' => $name
		);
	}


	/**
	 * public function fieldString (string/array $realname, [string $newname])
	 *
	 * return false/string
	 */
	public function fieldString ($realname, $newname = '') {
		if (is_array($realname)) {
			$newname = $realname['newname'];
			$realname = $realname['realname'];
		} else if (!$realname) {
			return false;
		}

		if ($newname && $newname != $realname) {
			$realname .= '['.$newname.']';
		}

		return $realname;
	}


	/**
	 * public function fieldInfo (string $field)
	 *
	 * return false/array
	 */
	public function fieldInfo ($field) {
		if (!($format = $this->getFormat($field))) {
			return false;
		}

		$pieces = explode('-', $field, 3);

		switch (count($pieces)) {
			case 1:
				return array(
					'field' => $pieces[0],
					'language' => (count($format->languages) == 1) ? '' : null,
					'subformat' => $format->settings[''] ? '' : null
				);

			case 2:
				if ($format->settings[$pieces[1]]) {
					return array(
						'field' => $pieces[0],
						'language' => (count($format->languages) == 1) ? '' : null,
						'subformat' => $pieces[1]
					);
				}

				if (in_array($pieces[1], $format->languages)) {
					return array(
						'field' => $pieces[0],
						'language' => $pieces[1],
						'subformat' => $format->settings[''] ? '' : null
					);
				}

				return false;

			case 3:
				if (in_array($pieces[1], $format->languages) && $format->settings[$pieces[2]]) {
					return array(
						'field' => $pieces[0],
						'language' => $pieces[1],
						'subformat' => $pieces[2]
					);
				}
		}

		return false;
	}


	/**
	 * private function tableString (string/array $realname, [string $newname], [string $name], [string $direction])
	 *
	 * return false/string
	 */
	private function tableString ($realname, $newname = '', $name = '', $direction = '') {
		if (is_array($realname)) {
			$name = $realname['name'];
			$direction = $realname['direction'];
			$newname = $realname['newname'];
			$realname = $realname['realname'];
		} else if (!$realname) {
			return false;
		}

		if ($newname && $newname != $realname) {
			$realname .= '['.$newname.']';
		}

		if ($direction) {
			$realname .= '-'.$direction;
		}

		if ($name) {
			$realname .= '('.$name.')';
		}

		return $realname;
	}


	/**
	 * public function getFormat (string $field)
	 *
	 * return object/false
	 */
	public function getFormat ($field) {
		list($field) = explode('-', $field, 2);

		if ($this->formats[$field]) {
			return $this->formats[$field];
		}

		return false;
	}


	/**
	 * public function selectField (string $field, [string $language])
	 *
	 * return string
	 */
	public function selectField ($field, $language = 'all') {
		$field = $this->selectFields($field, $language, '', false);

		return current($field);
	}

	/**
	 * public function selectFields ([string/array $fields], [string $language], [string $prefix], [boolean $force_id])
	 *
	 * return array
	 */
	public function selectFields ($fields = '*', $language = 'all', $prefix = '', $force_id = true) {
		if ($fields == '**') {
			$fields = array_keys($this->formats);
		} else if ($fields == '*') {
			$fields = array();

			foreach ($this->formats as $format_name => $format) {
				if ($format->format == 'id_relation') {
					continue;
				}

				$fields[] = $format_name;
			}
		}

		$new_fields = array();

		foreach ((array)$fields as $field) {
			$field = $this->fieldArray($field);

			if (!($info = $this->fieldInfo($field['realname']))) {
				$this->Debug->error('db', __('The field "%s" does not exists', $field['realname']));

				return array();
			}

			if ($field['realname'] == $field['newname']) {
				$renamed = false;
				$field['newname'] = $info['field'];
				$subformat_suffix = ($info['subformat'] || is_null($info['subformat'])) ? true : false;
			} else {
				$renamed = true;
				$subformat_suffix = is_null($info['subformat']) ? true : false;
			}

			if ($renamed && !is_null($info['language']) && !is_null($info['subformat'])) {
				$language_suffix = $subformat_suffix = false;
			} else {
				$language_suffix = ($language == 'all' && (is_null($info['language']) || ($info['language']))) ? true : false;
			}

			$format = $this->getFormat($field['realname']);

			foreach ($format->getFields($field['realname'], $language) as $realname => $field_info) {
				$newname = $prefix.$field['newname'];

				if ($language_suffix) {
					$newname .= '-'.$field_info['language'];
				}

				if ($subformat_suffix) {
					$newname .= '-'.$field_info['subformat'];
				}

				$new_fields[] = $this->fieldString($realname, $newname);
			}
		}

		//Add id field
		if ($force_id && !in_array('id', $new_fields)) {
			array_unshift($new_fields, 'id');
		}

		return $new_fields;
	}


	/**
	 * public function explodeData (array $data, [string $language])
	 *
	 * Group data fields into rows, formats, languages and subformats
	 *
	 * return array
	 */
	public function explodeData ($data, $language = '') {
		$return = array();

		if (!isNumericalArray($data) || !isMultidimensionalArray($data)) {
			$data = array($data);
		}

		foreach ($data as $num_row => $row) {
			foreach ($row as $field => $value) {
				//Literal field
				if (is_int($field) && is_string($value)) {
					$return[$num_row][$field] = $value;
					continue;
				}

				if (!($info = $this->fieldInfo($field))) {
					$this->Debug->error('table', __('The field "%s" doesn\'t exist', $field));
					return false;
				}

				$format = $this->getFormat($info['field']);

				//Fix languages
				$ok = false;

				if (is_array($value)) {
					$ok = true;

					foreach (array_keys($value) as $lang) {
						if (!isset($format->fields[$lang])) {
							$ok = false;
							break;
						}
					}
				}

				if (!is_array($value) || !$ok) {
					if (is_null($info['language'])) {
						$value = array((in_array($language, $format->languages) ? $language : $format->default_language) => $value);
					} else {
						$value = array($info['language'] => $value);
					}
				}

				//Fix subformat
				$ok = true;

				foreach ($value as $lang => $subs) {
					if (!is_array($subs)) {
						$ok = false;
						break;
					}

					foreach (array_keys($subs) as $sub) {
						if (!in_array($sub, $format->subformats)) {
							$ok = false;
							break 2;
						}
					}
				}

				if (!$ok) {
					if (is_null($info['subformat'])) {
						reset($format->settings);
						$subformat = key($format->settings);
					} else {
						$subformat = $info['subformat'];
					}

					foreach ($value as $lang => $subs) {
						$value[$lang] = array($subformat => $subs);
					}
				}

				$return[$num_row][$info['field']] = arrayMergeReplaceRecursive((array)$return[$num_row][$info['field']], $value);
			}
		}

		return $return;
	}


	/**
	 * public function implodeData (array $data)
	 *
	 * implode fields grouped with explodeFields function
	 *
	 * return array
	 */
	public function implodeData ($data) {
		$return = array();

		foreach ($data as $num_row => $row) {
			if (!is_array($row)) {
				continue;
			}

			foreach ($row as $format => $languages) {
				//Literal field
				if (is_int($format) && is_string($languages)) {
					$return[$num_row][$format] = $languages;
					continue;
				}

				foreach ($languages as $language => $fields) {
					$language = $language ? '-'.$language : '';

					foreach ($fields as $field => $value) {
						$field = $field ? '-'.$field : '';

						$return[$num_row][$format.$language.$field] = $value;
					}
				}
			}
		}

		return $return;
	}

	/**
	 * public function checkValues (array $data)
	 *
	 * Execute the format check function and return the errors
	 *
	 * return array
	 */
	public function checkValues ($data) {
		$errors = array();

		foreach ($data as $num_row => $row) {
			foreach ($row as $format => $languages) {
				//Literal field
				if (is_int($format)) {
					continue;
				}

				$format = $this->getFormat($format);

				foreach ($languages as $language => $fields) {
					if (!$format->check($fields)) {
						$errors[$num_row][$format->name][$language] = $format->getErrors();
					}
				}
			}
		}

		return $errors;
	}


	/**
	 * private function getDefaultValues ()
	 *
	 * return array
	 */
	public function getDefaultValues () {
		$default_values = array();

		foreach ($this->formats as $format) {
			if (in_array($format->format, array('id', 'id_relation'))) {
				continue;
			}

			foreach ($format->languages as $language) {
				$default_values[$format->name][$language] = $format->getDefaultValues();
			}
		}

		return $default_values;
	}


	/**
	 * public function valueDB (array $values, [array $ids])
	 *
	 * Execute de format valueDB function
	 *
	 * return boolean
	 */
	public function valueDB ($values, $ids = array()) {
		foreach ($values as $num_row => &$row) {
			foreach ($row as $format => &$languages) {
				//Literal field
				if (is_int($format)) {
					continue;
				}

				$format = $this->getFormat($format);

				foreach ($languages as $language => &$fields) {
					$fields = $format->valueDB($this->Data, $fields, $language, $ids[$num_row]);

					if ($fields === false) {
						unset($languages[$language]);
					}
				}
			}
		}

		return $values;
	}


	/**
	 * public function getFormatSettings (string $field, [string $name], [string $compare])
	 *
	 * return mixed/false
	 */
	public function getFormatSettings ($field, $name = '', $compare = '') {
		$field = $this->fieldInfo($field);
		$format = $this->getFormat($field['field']);

		$settings = $format->settings[$field['subformat']];

		if (!$settings) {
			return false;
		}

		if ($name) {
			if ($compare) {
				return ($settings[$name] == $compare) ? true : false;
			}

			return $settings[$name];
		}

		return $settings;
	}


	/**
	 * public function setFormatSettings (string $field, string/array $name, [string $value])
	 *
	 * return boolean
	 */
	public function setFormatSettings ($field, $name, $value = '') {
		$format = $this->getFormat($field);

		$settings = $format->settings[$field];

		if (!$format->settings[$field]) {
			return false;
		}

		if (is_array($name)) {
			$format->settings[$field] = array_merge($format->settings[$field], $name);
			return true;
		}

		$format->settings[$field][$name] = $value;

		return true;
	}


	/**
	 * public function getRelations ()
	 *
	 * return array
	 */
	public function getRelations () {
		if (!$this->relations) {
			return array();
		}

		return $this->relations;
	}


	/**
	 * public function getRelation (string $table, [string $name], [string $direction])
	 *
	 * return false/object
	 */
	public function getRelation ($table, $name = '', $direction = '') {
		if (!$table) {
			return false;
		}

		$rel_name = $this->tableString($table, '', $name, $direction);

		return $this->relations[$rel_name];
	}


	/**
	 * public function related (string $table, [string $name], [string $direction])
	 *
	 * return boolean
	 */
	public function related ($table, $name = '', $direction = '') {
		if (!$table) {
			return false;
		}

		$rel_name = $this->tableString($table, '', $name, $direction);

		return is_object($this->relations[$rel_name]);
	}


	/**
	 * private function setFormatsConfig ()
	 *
	 * return none
	 */
	private function setFormatsConfig () {
		$this->formats = $this->fields = array();

		//Id format is required in all tables
		$this->formats['id'] = new \data\formats\Id($this->table, 'id');
		$this->fields['id'] = array(
			'format' => 'id',
			'format_type' => 'id',
			'subformat' => '',
			'field' => 'id',
			'language' => 'default',
			'real_field' => 'id',
			'default_language' => 0
		);

		//Set remaining formats
		foreach ($this->settings['table'] as $field => $settings) {
			if ($field == 'id') {
				continue;
			}

			if (!is_array($settings)) {
				$settings = array('format' => $settings);
			}

			$class_name = '\\data\\formats\\'.ucfirst($settings['format']);
			$class_file = filePath('phpcan/includes|data/formats/class_'.$settings['format'].'.php');

			if (!is_file($class_file)) {
				$this->Debug->fatalError(__('Format "%s" for field "%s" in table "%s" does not exists!', $settings['format'], $field, $this->table));
			}

			$this->formats[$field] = new $class_name($this->table, $field, $settings);
		}

		//Remove table settings
		unset($this->settings['table']);
	}


	/**
	 * private function setRelationsConfig ()
	 *
	 * return none
	 */
	private function setRelationsConfig () {
		$this->relations = array();

		foreach ((array)$this->settings['relations'] as $name => $settings) {
			$class_name = 'data\\relations\\Relation_'.str_replace(' ', '_', $settings['mode']);
			$this->relations[$name] = new $class_name($this->Data, $settings);
		}

		//Remove relations settings
		unset($this->settings['relations']);
	}
}
?>
